﻿// COPYRIGHT 2012, Justin Michalczak
//
// This file is licenced to the public under the Apache
// License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

using System;
using System.Collections.Generic;
using System.Linq;

using CopperVineSoftware.Stackr.Parser.Lexemes;

namespace CopperVineSoftware.Stackr.Parser
{
    /// <summary>Representation of a Stackr program.</summary>
    /// <remarks>
    ///     A Stackr program consists of a series of top-level definitions.
    ///     One definition should be a <see cref="FunctionDefinition"/> named <c>main</c>, defining the entry-point of the program.
    /// </remarks>
    public class Program
    {
        /// <summary>Evaluates a group of definitions for duplicate names.</summary>
        /// <param name="definitions">Definitions to evaluate.</param>
        /// <returns>A sequence of all duplicated definition names.</returns>
        /// <exception cref="ArgumentNullException">If the <paramref name="definitions"/> parameter is <c>null</c>.</exception>
        public static IEnumerable<string> GetDuplicatedDefinitionNames(IEnumerable<Definition> definitions)
        {
            if (definitions == null)
                throw new ArgumentNullException("definitions");

            HashSet<string> names = new HashSet<string>();
            foreach (Definition definition in definitions)
            {
                if (!(names.Add(definition.Name)))
                    yield return definition.Name;
            }
        }

        private readonly IEnumerable<Definition> definitions;
        private readonly FunctionDefinition main;

        /// <summary>The definitions making up this program, excluding the main function.</summary>
        public IEnumerable<Definition> Definitions
        {
            get
            {
                return this.definitions;
            }
        }

        /// <summary>The main function of this program.</summary>
        public FunctionDefinition Main
        {
            get
            {
                return this.main;
            }
        }

        /// <summary>Constructor.</summary>
        /// <param name="definitions">Definitions making up the program.</param>
        /// <exception cref="ArgumentNullException">If the <paramref name="definitions"/> parameter is <c>null</c>.</exception>
        /// <exception cref="DuplicateDefinitionException">If any two definitions bind to the same name.</exception>
        /// <exception cref="NoMainFunctionDefinedException">If there is not a <see cref="FunctionDefinition"/> bound to the name <c>"main"</c>.</exception>
        public Program(IEnumerable<Definition> definitions)
        {
            if (definitions == null)
                throw new ArgumentNullException("definitions");

            // Check for duplicate definitions.
            IEnumerable<string> duplicatedNames = GetDuplicatedDefinitionNames(definitions);
            if (duplicatedNames.Count() > 0)
                throw new DuplicateDefinitionException(duplicatedNames);

            // Check for "main" and verify that it is a function.
            Definition main = definitions.FirstOrDefault((d) => d.Name == "main");
            if (main == default(Definition) || !(main is FunctionDefinition))
                throw new NoMainFunctionDefinedException();

            this.definitions = definitions.Where((d) => !(object.ReferenceEquals(d, main)));
            this.main = main as FunctionDefinition;
        }

        /// <summary>Retrieves a string representation of this program.</summary>
        /// <returns>A string that representation of this program.</returns>
        public override string ToString()
        {
            return string.Join("\n", this.definitions.Select<Definition, string>((d) => d.ToString()));
        }
    }
}
